package column;

import java.util.ArrayList;

import processing.core.PApplet;
import processing.core.PVector;
import processing.dxf.*;
import peasy.*;
import controlP5.*;
 

public class Profile extends PApplet {

	
	PeasyCam cam;
	ControlP5 cp5;
	// if labels appear in the constructor of the slider, they become parameters
	int slider1 = 20;
	int slider2 =10;
	boolean exportDXF;
	int nLayer=50;
	float layerHeight=5f;
	int nSegs=128;
	 
	public void setup() {
	  size(1200,700,P3D);
	  cp5 = new ControlP5(this);
	  cp5.addSlider("slider1")
	     .setPosition(20,20)
	     .setRange(0,100);
	  cp5.addSlider("slider2")
	     .setPosition(20,40)
	     .setRange(0,100);
	  cp5.setAutoDraw(false);
	  cam=new PeasyCam(this,100);
	}
	  
	public void draw() {
	  // we stop camera while mouse over sliders
	  if (mouseX<200){
	    cam.setActive(false);
	  }
	  else{
	    cam.setActive(true);
	  }
	  hint(ENABLE_DEPTH_TEST);
	  
	  // start the recording for export
	  if (exportDXF) {
	    camera();// make sure we always have the same cameraposition for each export
	    beginRaw(DXF, sketchPath("")+timeStamp()+".dxf");
	  }
	   
	  // here your drawing
	  lights();
	  background(0);
	 fill(255);
	 
	 // stroke(255);
	  noStroke();
	  translate(0,0,-nLayer*layerHeight*0.5f);
	  ArrayList<PVector> pointsOld=new ArrayList<PVector>();
	  for (int j=0;j<nLayer;j++) {
	    ArrayList<PVector> points=constructRecursive(j);
	    if (j>0) {
	     
	     // beginShape(QUAD_STRIP);
	      for (int i=0;i<points.size()+1;i++) {
	        PVector point=points.get(i%points.size());
	        PVector pointOld=pointsOld.get(i%points.size());
	       // vertex(pointOld.x, pointOld.y, pointOld.z*layerHeight);
	        //vertex(point.x, point.y, point.z*layerHeight);
	        extrudeLine(pointOld.x, pointOld.y, pointOld.z*layerHeight,point.x, point.y, point.z*layerHeight,0.1f,4);
	       // line(pointOld.x, pointOld.y, pointOld.z*layerHeight,point.x, point.y, point.z*layerHeight);
	      }
	    //  endShape();
	      
	    }
	    pointsOld=points;
	  }
	  
	  // this part stops to record the drawing if exportDXF is true
	  if (exportDXF) {
	    exportDXF=false;
	    println("exported");
	    endRaw();
	  }
	  
	  // change the drawing mode so we can draw the GUI in 2D
	  hint(DISABLE_DEPTH_TEST);
	  cam.beginHUD();
	  noLights();
	  cp5.draw();
	  cam.endHUD();
	}
	 
	// consctruct a paramtric polygon as ArrayList of PVector
	public ArrayList<PVector> constructPolygon(float z) {
	  float radius=sin(z/slider1)*10+20;
	  float starFactor=z*1.0f/slider2;
	  float phase=0;
	   
	  ArrayList<PVector> points=new ArrayList<PVector>();
	  for (int i=0;i<nSegs;i++) {
	    float angle=2*PI*i/nSegs+phase;
	    float r=radius+pow((cos(angle*starFactor)), 3)*8;
	    float x=r*cos(angle);
	    float y=r*sin(angle);
	    points.add(new PVector(x, y, z));
	  }
	  mirrorHalf(points);
	  return points;
	}
	 
	// mirror half of the points 
	public  void mirrorHalf(ArrayList<PVector> points) {
	  int mirrorAxis=points.size()/2;
	  for (int i=0;i<mirrorAxis;i++) {
	    PVector p1=points.get(i);
	    PVector p2=points.get(points.size()-i-1);
	    p2.x=p1.x;
	    p2.y=-p1.y;
	  }
	}
	 
	// returns a string encoding the current time
	public String timeStamp() {
	  return year() + nf(month(), 2) + nf(day(), 2) + "-" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2);
	}
	// if the key e is pressed, we turn on the recording
	public void keyPressed() {
	  if (key=='e')exportDXF=true;

}	
	public void extrudeLine(float x1,float y1,float z1,float x2,float y2,float z2,float r,int seg){
		ArrayList<PVector>r1=constructCircle(r,seg,x1,y1,z1);
		ArrayList<PVector>r2=constructCircle(r,seg,x2,y2,z2);
		beginShape(QUAD_STRIP);
		for (int i=0;i<=r1.size();i++){
			int index=i%r1.size();
			PVector v1=r1.get(index);
			PVector v2=r2.get(index);
			vertex(v1.x,v1.y,v1.z);
			vertex(v2.x,v2.y,v2.z);
		}
		endShape();
	}
	// returns an ArrayList of PVectors of a regular sphere
	public ArrayList<PVector> constructCircle(float radius, int nSegments,float cx,float cy, float cz) {
	  ArrayList<PVector> points=new ArrayList<PVector>();
	  for (int i=0;i<nSegments;i++) {
	    float angle=2*PI*i/nSegments;
	    float x=radius*cos(angle);
	    float y=radius*sin(angle);
	    points.add(new PVector(x+cx, y+cy, cz));
	  }
	  return points;
	}
	 
	// returns an ArrayList of PVectors as a polygon with changing shape depending on z
	// in each iteration, a new point in each segment between two points is inserted
	// this point is shifted with a certaing factor orthogonal to this segment.
	// this splitting happens in the method splitLines
	public ArrayList<PVector> constructRecursive(float z) {
	  ArrayList<PVector> points=constructCircle(20, 7, 0,0,z);
	  points=splitLines(points, sin(z/5.0f)*10);
	  points=splitLines(points, 40-z);
	  points=splitLines(points, -2);
	  points=splitLines(points, 4);
	  points=splitLines(points, -1);
	  points=splitLines(points, 2);
	  points=splitLines(points, 1);
	  points=splitLines(points, 2);
	  // points=splitLines(points, -0.5f);
	  return points;
	}
	
	// returns the normal of a segment
	PVector getNormal(PVector p0, PVector p1) {
	  float d=p0.dist(p1);
	  PVector n=new PVector((p1.y-p0.y)/d, (p0.x-p1.x)/d);
	  return n;
	}
	 
	// returns a new ArrayList of PVectors
	// each segment between two points is splitted at the center
	// this splitpoint is shifted along the direction of the normal of the segment
	// the amoutn of shifting is equals the parameter shift
	public ArrayList<PVector> splitLines(ArrayList<PVector> inputPoints, float shift) {
	  ArrayList<PVector> newpoints=new ArrayList<PVector>();
	  int nP=inputPoints.size();
	  for (int i=0;i<nP;i++) {
	    PVector p0=inputPoints.get(i);
	    PVector p1=inputPoints.get((i+1)%nP);
	    PVector n=getNormal(p0, p1);
	    n.mult(shift);
	    newpoints.add(p0.get());
	    PVector center=new PVector((p1.x+p0.x)*0.5f+n.x, (p1.y+p0.y)*0.5f+n.y, (p1.z+p0.z)*0.5f);
	    newpoints.add(center);
	  }
	  return newpoints;
	}
}
